package com.xlkj.ms.tools.aop.aspects;

import com.alibaba.fastjson.JSON;
import com.xlkj.ms.tools.aop.annotations.MicroService;
import com.xlkj.ms.tools.aop.utils.ValidParamsUtils;
import com.xlkj.ms.tools.core.commons.exceptions.CustomizeBizException;
import com.xlkj.ms.tools.core.request.AbstractRequestWithValidate;
import com.xlkj.ms.tools.core.response.ResponseWrapper;
import com.xlkj.ms.tools.core.utils.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

import java.io.Serializable;
import java.lang.reflect.Method;

/**
 * 微服务切面 负责请求日志打印、参数校验、异常处理、返回报文打印
 * @author zhuqifeng
 * @date 2021/11/10 14:05
 * @package com.xlkj.ms.tools.aop.aspects
 */
@Component
@Aspect
public class MSAspect {

    private final static Logger logger = LoggerFactory.getLogger(MSAspect.class);

    @Pointcut("@annotation(microService)")
    private void pointCutMethod(MicroService microService) {
    }

    @Around(value = "pointCutMethod(microService)", argNames = "pjp,microService")
    public <T> Object doAround(ProceedingJoinPoint pjp, MicroService microService) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object object = null;
        //获取操作名，如果不传，那么取class.method作为操作名
        String operMsg = StringUtils.isBlank(microService.operCode()) ? microService.value() : microService.operCode();
        operMsg = StringUtils.isBlank(operMsg)
                ? pjp.getTarget().getClass().getCanonicalName() + "." + ((MethodSignature) pjp.getSignature()).getMethod().getName()
                : microService.operCode();

        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        //获取返回类
        Class<?> returnClass = method.getReturnType();

        try {
            // 获取请求参数
            Object[] args = pjp.getArgs();
            if (null == args || args.length == 0) {
                logger.info("操作[{}]开始,没有参数", operMsg);
            } else {
                for (int i=0; i<args.length; i++){
                    Object arg = args[i];
                    if(microService.printParams()){
                        //打印参数
                        printArg(microService, operMsg, arg, i);
                    }
                    //参数校验
                    validArg(microService, arg);
                }
            }
            // 执行方法块中的逻辑
            object = pjp.proceed();
            if (object instanceof ResponseWrapper) {
                object.getClass().getMethod("wrapperSuccess").invoke(object);
            } else {
                return object;
            }
            // 如果没有异常，那么返回成功
        } catch (CustomizeBizException e) {
            object = handleBizException(returnClass, e);
        } catch (IllegalArgumentException e) {
            object = handleIllegalArgumentException(returnClass, e, e.getMessage());
        } catch (Throwable e) {
            object = handleSystemException(returnClass, e);
        }finally {
            stopWatch.stop();
            if (!isVoid(returnClass)) {
                if (microService.printParams()) {
                    //debug或者允许打印的时候才打印
                    if (logger.isDebugEnabled() || microService.printResponse()) {
                        logger.info("[{}][耗时：{}ms],返回报文:{}", operMsg, stopWatch.getTotalTimeMillis(), JSON.toJSONString(object, microService.serializerFeature()));
                    } else {
                        //不允许打印返回报文的时候，只打印耗时
                        logger.info("[{}][耗时：{}ms]", operMsg, stopWatch.getTotalTimeMillis());
                    }
                }
            } else {
                if (microService.printParams()) {
                    //如果返回是void，那么会打印耗时
                    logger.info("[{}][耗时：{}ms]", operMsg, stopWatch.getTotalTimeMillis());
                }
            }
        }
        return object;
    }

    /**
     * 处理参数错误
     * @param msg 响应内容
     * @return
     */
    private <T> Object handleIllegalArgumentException(Class<T> clazz, Exception e, String... msg) throws Exception {
        if (isVoid(clazz)) {
            return null;
        }
        if (isAbstractResponseWithSupCl(clazz)) {
            Object obj = clazz.newInstance();
            clazz.getMethod("wrapperIllegalArgument",String[].class).invoke(obj, (Object) msg);
            return obj;
        } else {
            //交由系统异常处理
            throw e;
        }
    }

    /**
     * 处理业务异常
     * @return
     */
    private <T> Object handleBizException(Class<T> clazz, CustomizeBizException e) throws Exception {
        if (isVoid(clazz)) {
            return null;
        }
        return e.wrapperBizFailed(clazz);
    }

    /**
     * 处理系统异常
     * @return
     */
    private <T> Object handleSystemException(Class<T> clazz, Throwable e) throws Throwable {
        logger.error(e.getMessage(), e);
        if (isVoid(clazz)) {
            return null;
        }
        if (isAbstractResponseWithSupCl(clazz)) {
            Object obj = clazz.newInstance();
            Method method = clazz.getMethod("wrapperSystemError");
            method.invoke(obj);
            return obj;
        } else {
            throw e;
        }
    }

    /**
     * 判断接口返回是不是void
     * @param clazz
     * @return
     */
    private boolean isVoid(Class<?> clazz) {
        return "void".equalsIgnoreCase(clazz.getName());
    }

    /**
     * 校验参数
     * @param microService 切面注解
     * @param arg 参数对象
     * @throws IllegalAccessException
     */
    private void validArg(MicroService microService, Object arg) throws IllegalAccessException {
        if(arg instanceof AbstractRequestWithValidate){
            /** 参数校验 */
            if(microService.needValidParams()){
                ValidParamsUtils.validateAnnotation(arg, microService.validateGroups());
            }
        }
    }

    /**
     * 打印参数
     * @param microService 切面注解
     * @param operMsg 操作名称
     * @param arg 参数对象
     * @param i 参数所在第几个
     */
    private void printArg(MicroService microService, String operMsg, Object arg, int i){
        /** 打印请求报文 */
        if (logger.isDebugEnabled() || microService.printRequest()) {
            if(arg instanceof Serializable){
                logger.info("[{}]第[{}]个请求报文：{}", operMsg, i+1, JSON.toJSONString(arg, microService.serializerFeature()));
            }else{
                logger.info("[{}]第[{}]个请求不是序列化参数，那么打印toString报文：{}", operMsg, i+1, arg.toString());
            }
        } else if(i==0){
            logger.info("进入[{}]请求", operMsg);
        }
    }

    /**
     * 是否是抽象的基础响应类（包括所有的父类）
     * @param clazz
     * @return
     */
    private boolean isAbstractResponseWithSupCl(Class clazz){
        try {
            return clazz.newInstance() instanceof ResponseWrapper;
        } catch (Exception e) {
            return false;
        }
    }

}
